﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Threading;
using CommonUtils.Logging;

namespace ProcessLauncher
{
    /// <summary>
    /// Process launcher class itself
    /// </summary>
    internal class ProcessLauncher
    {
        #region Singleton

        /// <summary>
        /// Default constructor
        /// </summary>
        private ProcessLauncher()
        {
        }

        /// <summary>
        /// The only instance
        /// </summary>
        private static readonly ProcessLauncher instance = new ProcessLauncher();

        /// <summary>
        /// The only instance
        /// </summary>
        public static ProcessLauncher Instance
        {
            [DebuggerStepThrough]
            get
            {
                return instance;
            }
        }

        #endregion

        #region Defaults

        /// <summary>
        /// Default timeout for repeat
        /// </summary>
        private readonly TimeSpan defaultRepeatTimeout = new TimeSpan(0, 0, 0, 0, 500);

        /// <summary>
        /// Default timeout for repeat
        /// </summary>
        public TimeSpan DefaultRepeatTimeout
        {
            [DebuggerStepThrough]
            get
            {
                return defaultRepeatTimeout;
            }
        }

        /// <summary>
        /// Default timeout
        /// </summary>
        private readonly TimeSpan defaultRunTimeout = new TimeSpan(0);

        /// <summary>
        /// Default timeout
        /// </summary>
        public TimeSpan DefaultRunTimeout
        {
            [DebuggerStepThrough]
            get
            {
                return defaultRunTimeout;
            }
        }

        /// <summary>
        /// Default wait for conditions timeout
        /// </summary>
        private readonly TimeSpan defaultWaitTimeout = TimeSpan.MaxValue;

        /// <summary>
        /// Default wait for conditions timeout
        /// </summary>
        public TimeSpan DefaultWaitTimeout
        {
            [DebuggerStepThrough]
            get
            {
                return defaultWaitTimeout;
            }
        }

        #endregion

        /// <summary>
        /// Launch process
        /// </summary>
        /// <param name="fileName">Process executable file name</param>
        /// <param name="arguments">Process start arguments</param>
        /// <param name="runTimeout">Timeout before launch process</param>
        /// <param name="prerequirementProcesses">Wait for specified processes before start</param>
        /// <param name="prerequirementConnections">Wait for specified network connections before start</param>
        /// <param name="repeatTimeout">Timeout before start condition recheck</param>
        /// <param name="waitTimeout">Total runTimeout to wait for conditions</param>
        public void Launch(string fileName,
                           string arguments,
                           TimeSpan? runTimeout,
                           IEnumerable<string> prerequirementProcesses,
                           IEnumerable<string> prerequirementConnections,
                           TimeSpan? repeatTimeout,
                           TimeSpan? waitTimeout)
        {
            Process process = new Process();
            ProcessStartInfo processStartInfo = new ProcessStartInfo(fileName, arguments);

            process.StartInfo = processStartInfo;

            TimeSpan actualRunTimeout = runTimeout ?? DefaultRunTimeout;

            if (actualRunTimeout.TotalMilliseconds > 0)
            {
                Logger.Instance.PrintMessage(string.Format("Waiting for {0} ...", actualRunTimeout));
                Thread.Sleep(actualRunTimeout);
            }

            DateTime startTime = DateTime.Now;

            Logger.Instance.PrintMessage("Checking prerequirements...");

            TimeSpan actualWaitTimeout = waitTimeout ?? DefaultWaitTimeout;

            do
            {
                if (CheckConditions(prerequirementProcesses, prerequirementConnections))
                {
                    if (!string.IsNullOrEmpty(fileName))
                    {
                        Logger.Instance.PrintMessage(string.Format("Starting '{0}'...", fileName));
                        process.Start();
                    }
                    return;
                }
                Thread.Sleep(repeatTimeout ?? DefaultRepeatTimeout);
            }
            while ((DateTime.Now - startTime) < (actualWaitTimeout));

            Logger.Instance.PrintMessage(string.Format("Prerequirements was not statisfied in {0}. Launch canceled.",
                                                       actualWaitTimeout));
        }

        /// <summary>
        /// Check for conditions statisfied
        /// </summary>
        /// <param name="processList">List of process names to wait for</param>
        /// <param name="connectionList">List of connection names to wait for</param>
        /// <returns>True if all conditions statisfied, otherwise - false</returns>
        private bool CheckConditions(IEnumerable<string> processList, IEnumerable<string> connectionList)
        {
            if (processList != null)
            {
                if (!CheckProcessesExists(processList))
                {
                    return false;
                }
            }

            if (connectionList != null)
            {
                if (!CheckConnectionsExests(connectionList))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Check for processes exists
        /// </summary>
        /// <param name="processList">List of process names</param>
        /// <returns>True if all processes exists, otherwise - false</returns>
        private bool CheckProcessesExists(IEnumerable<string> processList)
        {
            foreach (string process in processList)
            {
                if (!CheckProcessExists(process))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Check for process existance
        /// </summary>
        /// <param name="processName">Process name</param>
        /// <returns>True if process exists, otherwise - false</returns>
        private bool CheckProcessExists(string processName)
        {
            foreach (Process process in Process.GetProcesses())
            {
                if (process.ProcessName.ToLower() == processName.ToLower())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Check for connections exists
        /// </summary>
        /// <param name="connectionList">List of connaction names</param>
        /// <returns>True if all connections exists, otherwise - false</returns>
        private bool CheckConnectionsExests(IEnumerable<string> connectionList)
        {
            foreach (string connection in connectionList)
            {
                if (!CheckConnectionExists(connection))
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Check for connection existance
        /// </summary>
        /// <param name="connectionName">Connection name</param>
        /// <returns>True if connection exists, otherwise - false</returns>
        private bool CheckConnectionExists(string connectionName)
        {
            foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
            {
                if (networkInterface.Name.ToLower() == connectionName.ToLower() &&
                    networkInterface.OperationalStatus == OperationalStatus.Up)
                {
                    return true;
                }
            }
            return false;
        }
    }
}